﻿using FCSChart.Axis;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;

namespace FCSChart.Series
{
    /// <summary>
    /// 直方图
    /// </summary>
    public class HistogramSeries : ISeries
    {

        /// <summary>
        /// 直方图等份数量
        /// </summary>
        public int Count
        {
            get { return (int)GetValue(CountProperty); }
            set { SetValue(CountProperty, value); }
        }
        public static readonly DependencyProperty CountProperty = DependencyProperty.Register("Count", typeof(int), typeof(HistogramSeries), new PropertyMetadata(512, NeedRedrawingProperty_Changed));

        /// <summary>
        /// 是否平滑
        /// </summary>
        public bool Smooth
        {
            get { return (bool)GetValue(SmoothProperty); }
            set { SetValue(SmoothProperty, value); }
        }
        public static readonly DependencyProperty SmoothProperty = DependencyProperty.Register("Smooth", typeof(bool), typeof(HistogramSeries), new PropertyMetadata(false, NeedRedrawingProperty_Changed));

        /// <summary>
        /// 简单平滑算法的点数
        /// </summary>
        public int SmoothPointCount
        {
            get { return (int)GetValue(SmoothPointCountProperty); }
            set { SetValue(SmoothPointCountProperty, value); }
        }
        public static readonly DependencyProperty SmoothPointCountProperty = DependencyProperty.Register("SmoothPointCount", typeof(int), typeof(HistogramSeries), new PropertyMetadata(6));

        public HistogramSeries()
        {
            this.Tickness = 0;
            this.SeriesName = "Histogram";
        }

        internal override async void Drawing()
        {
            if (OwnerChart == null || OwnerChart.XSource == null || OwnerChart.XAxis == null || OwnerChart.YAxis == null) return;
            if (CancelTokenSource != null)
            {
                CancelTokenSource.Cancel();
                CancelTokenSource.Dispose();
            }
            CancelTokenSource = new CancellationTokenSource();
            try
            {
                var temps = await GetGeometries(CancelTokenSource.Token, OwnerChart.XSource, null, OwnerChart.XAxis, OwnerChart.YAxis, OwnerChart.Indexs, OwnerChart.XValueConverter, OwnerChart.YValueConverter, this.Fill, this.Stroke, this.GetGradientColor);
                if (temps != null)
                {
                    foreach (var temp in temps)
                    {
                        var g = Geometries.FirstOrDefault(p => p.Area == temp.Area);
                        if (g == null) Geometries.Add(temp);
                        else g.Stream = temp.Stream;
                    }
                    Drawing(Geometries);
                }
                if (CancelTokenSource != null)
                {
                    CancelTokenSource.Dispose();
                    CancelTokenSource = null;
                }
            }
            catch (TaskCanceledException) { }
        }

        internal override Task<List<StreamColor>> GetGeometries(CancellationToken token, IList xSource, IList ySource, IAxis xAxis, IAxis yAxis, IList<int> indexs, Func<object, double> xValueConverter, Func<object, double> yValueConverter, Color fill, Color stroke, Func<long, long, Color> getGradientColor)
        {
            var pointcount = xSource.Count;
            Func<double, double> valuetoaxisvalue = xAxis.ValueToAxisValue;
            Func<double, double> yvaluetoaxisvalue = yAxis.ValueToAxisValue;
            Func<double, ValueLocationConvertParam, double> ygetlocation = yAxis.GetValueLocation;
            var count = Count;
            var maxaxis = xAxis.MaxAxis;
            var minaxis = xAxis.MinAxis;
            var max = xAxis.Max;
            var min = xAxis.Min;
            var range = (maxaxis - minaxis) / count;
            var xGetLocationParam = xAxis.GetConvertParam();
            var height = this.ActualHeight;
            var areas = this.OwnerChart is ChartWithGraphicals tempchart && tempchart.AllAreas != null ? tempchart.AllAreas.ToArray() : null;
            var maxDegreeOfParallelism = MaxDegreeOfParallelism;
            var smooth = this.Smooth;
            var smoothpointcount = this.SmoothPointCount;

            var response = Task.Factory.StartNew(() =>
            {
                List<StreamColor> streams = new List<StreamColor>();

                #region 总直方图
                ConcurrentDictionary<double[], double> datas = new ConcurrentDictionary<double[], double>();//512份区间内的数量
                ConcurrentDictionary<int, double[]> dictionaries = new ConcurrentDictionary<int, double[]>();//区间顺序
                for (int i = 0; i < count; i++)
                {
                    if (token.IsCancellationRequested) return null;
                    var x1 = xAxis.GetAxisValueLocation(range * i + minaxis, xGetLocationParam);
                    var x2 = xAxis.GetAxisValueLocation(range * (i + 1) + minaxis, xGetLocationParam);
                    var key = new double[] { x1, x2 };
                    datas[key] = 0;
                    dictionaries[i] = key;
                }
                if (indexs == null)
                {
                    try
                    {
                        var result = Parallel.For(0, pointcount, new ParallelOptions() { CancellationToken = token, MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i, loop) =>
                        {
                            if (loop.IsStopped) return;
                            if (xSource == null || xSource.Count <= i || token.IsCancellationRequested) loop.Stop();
                            else
                            {
                                var tempv = xValueConverter == null ? Convert.ToDouble(xSource[i]) : xValueConverter(xSource[i]);
                                AddValue(datas, dictionaries, tempv, max, min, valuetoaxisvalue, minaxis, range, count);
                            }
                        });
                        if (!result.IsCompleted) return null;
                    }
                    catch (OperationCanceledException) { return null; }
                }
                else
                {
                    try
                    {
                        var result = Parallel.ForEach(indexs, new ParallelOptions() { CancellationToken = token, MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i, loop) =>
                        {
                            if (loop.IsStopped) return;
                            if (xSource == null || xSource.Count <= i || token.IsCancellationRequested) loop.Stop();
                            else
                            {
                                var tempv = xValueConverter == null ? Convert.ToDouble(xSource[i]) : xValueConverter(xSource[i]);
                                AddValue(datas, dictionaries, tempv, max, min, valuetoaxisvalue, minaxis, range, count);
                            }
                        });
                        if (!result.IsCompleted) return null;
                    }
                    catch (OperationCanceledException) { return null; }
                }
                var yMax = datas.Max(p => p.Value) * (smooth ? 1d : 1.2);
                yAxis.Dispatcher.Invoke(() =>
                {
                    yAxis.Max = yMax <= 0 ? 100000d : yMax;
                    yAxis.Min = 0;
                    yAxis.Drawing();
                });
                var yconvertparam = new ValueLocationConvertParam()
                {
                    MaxAxis = yvaluetoaxisvalue(yMax),
                    MinAxis = yvaluetoaxisvalue(0),
                    Length = yAxis.ActualHeight,
                    XYType = AxisType.Y
                };
                var streamGeometry = new StreamGeometry() { FillRule = FillRule.EvenOdd };
                using (StreamGeometryContext sgc = streamGeometry.Open())
                {
                    if (!FillStreamGeometryContext(token, sgc, datas, ygetlocation, yconvertparam, height, smooth, smoothpointcount)) return null;
                    sgc.Close();
                }
                streamGeometry.Freeze();
                streams.Add(new StreamColor() { Stream = streamGeometry, Fill = fill, Stroke = stroke });
                #endregion

                #region 门的直方图
                if (areas != null)
                {
                    if (!AddAreasStream(areas, pointcount, count, xSource, indexs, max, min, range, xAxis, minaxis, height, valuetoaxisvalue, xValueConverter, xGetLocationParam, yconvertparam, ygetlocation, token, maxDegreeOfParallelism, streams, smooth, smoothpointcount))
                        return null;
                }
                #endregion

                return streams;
            }, token);
            return response;
        }

        private bool AddAreasStream(Graphical.GraphicalArea[] areas, int pointcount, int count, IList xSource, IList<int> indexs, double max, double min, double range, IAxis xAxis, double minaxis, double height, Func<double, double> valuetoaxisvalue, Func<object, double> xValueConverter, ValueLocationConvertParam xGetLocationParam, ValueLocationConvertParam yconvertparam, Func<double, ValueLocationConvertParam, double> ygetlocation, CancellationToken token, int maxDegreeOfParallelism, List<StreamColor> streams, bool smooth, int smoothpointcount)
        {
            foreach (var area in areas)
            {
                if (area == null || area.InsideIndexs == null || area.InsideIndexs.Count <= 0) continue;
                var maxindex = area.InsideIndexs.Max();
                if (pointcount <= maxindex) continue;
                ConcurrentDictionary<double[], double> areadatas = new ConcurrentDictionary<double[], double>();//512份区间内的数量
                ConcurrentDictionary<int, double[]> areadictionaries = new ConcurrentDictionary<int, double[]>();//区间顺序
                for (int i = 0; i <= count; i++)
                {
                    if (token.IsCancellationRequested) return false;
                    var x1 = xAxis.GetAxisValueLocation(range * i + minaxis, xGetLocationParam);
                    var x2 = xAxis.GetAxisValueLocation(range * (i + 1) + minaxis, xGetLocationParam);
                    var key = new double[] { x1, x2 };
                    areadatas[key] = 0;
                    areadictionaries[i] = key;
                }
                var tempindexs = area.InsideIndexs;
                if (indexs != null) tempindexs = tempindexs.Intersect(indexs).ToArray();//取交集
                try
                {
                    var result = Parallel.ForEach(tempindexs, new ParallelOptions() { CancellationToken = token, MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i, loop) =>
                    {
                        if (loop.IsStopped) return;
                        if (xSource == null || xSource.Count <= i || token.IsCancellationRequested) loop.Stop();
                        else
                        {
                            var tempv = xValueConverter == null ? Convert.ToDouble(xSource[i]) : xValueConverter(xSource[i]);
                            AddValue(areadatas, areadictionaries, tempv, max, min, valuetoaxisvalue, minaxis, range, count);
                        }
                    });
                    if (!result.IsCompleted) return false;
                }
                catch (OperationCanceledException) { return false; }
                var stream = new StreamGeometry() { FillRule = FillRule.EvenOdd };
                using (StreamGeometryContext sgc = stream.Open())
                {
                    if (!FillStreamGeometryContext(token, sgc, areadatas, ygetlocation, yconvertparam, height, smooth, smoothpointcount)) return false;
                    sgc.Close();
                }
                stream.Freeze();
                streams.Add(new StreamColor() { Stream = stream, Fill = area.DisplayColor, Stroke = area.DisplayColor, Area = area });
            }
            return true;
        }

        private void AddValue(ConcurrentDictionary<double[], double> datas, ConcurrentDictionary<int, double[]> dictionaries, double tempv, double max, double min, Func<double, double> valuetoaxisvalue, double minaxis, double range, int count)
        {
            //if (tempv > max || tempv < min) return;
            if (tempv > max) tempv = max; else if (tempv < min) tempv = min;
            var value = (valuetoaxisvalue(tempv) - minaxis) / range;
            if (double.IsNaN(value) || double.IsInfinity(value)) return;
            if (value < 0) return;
            var temp = Convert.ToInt32(Math.Truncate(value));
            if (temp >= count) temp = count - 1;
            var td = dictionaries[temp];
            datas[td] += 1;
        }

        /// <summary>
        /// 填充适量数据
        /// </summary>
        /// <param name="token"></param>
        /// <param name="sgc"></param>
        /// <param name="datas"></param>
        /// <param name="ygetlocation"></param>
        /// <param name="yconvertparam"></param>
        /// <param name="height"></param>
        /// <returns></returns>
        private bool FillStreamGeometryContext(CancellationToken? token, StreamGeometryContext sgc, ConcurrentDictionary<double[], double> datas, Func<double, ValueLocationConvertParam, double> ygetlocation, ValueLocationConvertParam yconvertparam, double height, bool smooth, int smoothpointcount)
        {
            List<Point> points = new List<Point>();
            var tempdatas = datas.OrderBy(p => p.Key[0]).ToList();
            if (smooth)
            {
                var movitems = new List<double>();
                for (int i = 0; i < tempdatas.Count(); i++)
                {
                    if (token.HasValue && token.Value.IsCancellationRequested) return false;
                    var data = tempdatas[i];
                    if (i > smoothpointcount) movitems.RemoveAt(0);
                    movitems.Add(data.Value);
                    var yvalue = ygetlocation(movitems.Sum() / movitems.Count, yconvertparam);
                    if (double.IsNaN(data.Key[0]) || double.IsInfinity(data.Key[0]) || double.IsNaN(data.Key[1]) || double.IsInfinity(data.Key[1]) || double.IsNaN(yvalue) || double.IsInfinity(yvalue)) continue;
                    points.Add(new Point((data.Key[0] + data.Key[1]) / 2, yvalue));
                }
                if (token.HasValue && token.Value.IsCancellationRequested) return false;
                if (points.Count > 0)
                {
                    points.Add(new Point(points[points.Count - 1].X, height));
                    points.Add(new Point(points[0].X, height));
                    sgc.BeginFigure(new Point(points[0].X, height), true, true);
                    sgc.PolyLineTo(points, false, false);
                }
                else
                {
                    sgc.BeginFigure(new Point(0, 0), true, true);
                    sgc.LineTo(new Point(0, 0), false, false);
                }
            }
            else
            {
                foreach (var data in tempdatas)
                {
                    if (token.HasValue && token.Value.IsCancellationRequested) return false;
                    var yvalue = ygetlocation(data.Value, yconvertparam);
                    if (double.IsNaN(data.Key[0]) || double.IsInfinity(data.Key[0]) || double.IsNaN(data.Key[1]) || double.IsInfinity(data.Key[1]) || double.IsNaN(yvalue) || double.IsInfinity(yvalue)) continue;
                    points.Add(new Point(data.Key[0], yvalue));
                    points.Add(new Point(data.Key[1], yvalue));
                }
                if (token.HasValue && token.Value.IsCancellationRequested) return false;
                if (points.Count > 0)
                {
                    points.Add(new Point(points[points.Count - 1].X, height));
                    points.Add(new Point(points[0].X, height));
                    sgc.BeginFigure(new Point(points[0].X, height), true, true);
                    sgc.PolyLineTo(points, false, false);
                }
                else
                {
                    sgc.BeginFigure(new Point(0, 0), true, true);
                    sgc.LineTo(new Point(0, 0), false, false);
                }
            }
            return true;
        }

        #region 区域显示
        /// <summary>
        /// 添加区域
        /// </summary>
        /// <param name="area"></param>
        internal override void AddArea(Graphical.GraphicalArea area, IList xSource, IList ySource, IAxis xAxis, IAxis yAxis, IList<int> indexs, Func<object, double> xValueConverter, Func<object, double> yValueConverter)
        {
            if (area == null || Geometries.Any(p => p.Area == area)) return;
            var stream = GetAreaStream(area, xSource, ySource, xAxis, yAxis, indexs, xValueConverter, yValueConverter);
            if (stream != null)
            {
                lock (Geometries)
                {
                    if (Geometries.Any(p => p.Area == area)) return;
                    Geometries.Add(new StreamColor() { Stream = stream, Area = area, Fill = area.DisplayColor, Stroke = area.DisplayColor });
                }
                Drawing(Geometries);
            }
        }
        /// <summary>
        /// 删除区域
        /// </summary>
        /// <param name="area"></param>
        internal override void RemoveArea(Graphical.GraphicalArea area)
        {
            if (Geometries != null && area != null)
            {
                var temp = Geometries.FirstOrDefault(p => p.Area == area);
                if (temp != null && temp.Stream != null)
                {
                    Geometries.Remove(temp);
                    Drawing(Geometries);
                }
            }
        }
        /// <summary>
        /// 更新区域显示颜色
        /// </summary>
        /// <param name="area"></param>
        internal override void UpdateAreaDisplayColor(Graphical.GraphicalArea area)
        {
            if (Geometries != null && area != null)
            {
                var temp = Geometries.FirstOrDefault(p => p.Area == area);
                if (temp != null && temp.Stream != null)
                {
                    temp.Fill = temp.Stroke = area.DisplayColor;
                    Drawing(Geometries);
                }
            }
        }
        /// <summary>
        /// 更新区域索引
        /// </summary>
        /// <param name="area"></param>
        internal override void UpdateAreaIndexs(Graphical.GraphicalArea area, IList xSource, IList ySource, IAxis xAxis, IAxis yAxis, IList<int> indexs, Func<object, double> xValueConverter, Func<object, double> yValueConverter)
        {
            if (area == null || Geometries == null) return;
            var geometries = Geometries.FirstOrDefault(p => p.Area == area);
            if (geometries == null)
            {
                AddArea(area, xSource, ySource, xAxis, yAxis, indexs, xValueConverter, yValueConverter);
            }
            else
            {
                var stream = GetAreaStream(area, xSource, ySource, xAxis, yAxis, indexs, xValueConverter, yValueConverter);
                if (stream != null) geometries.Stream = stream;
                else lock (Geometries) Geometries.Remove(geometries);
                Drawing(Geometries);
            }
        }

        private StreamGeometry GetAreaStream(Graphical.GraphicalArea area, IList xSource, IList ySource, IAxis xAxis, IAxis yAxis, IList<int> indexs, Func<object, double> xValueConverter, Func<object, double> yValueConverter)
        {
            if (area == null || area.InsideIndexs == null || area.InsideIndexs.Count <= 0 || xSource == null || xSource.Count <= 0) return null;
            Func<double, double> xvaluetoaxisvalue = xAxis.ValueToAxisValue;
            ValueLocationConvertParam yconvertparam = yAxis.GetConvertParam();
            Func<double, ValueLocationConvertParam, double> ygetlocation = yAxis.GetValueLocation;
            var count = Count;
            var maxaxis = xAxis.MaxAxis;
            var minaxis = xAxis.MinAxis;
            var max = xAxis.Max;
            var min = xAxis.Min;
            var range = (maxaxis - minaxis) / count;
            var xGetLocationParam = xAxis.GetConvertParam();
            var height = this.ActualHeight;
            var maxDegreeOfParallelism = MaxDegreeOfParallelism;
            var smooth = this.Smooth;
            var smoothpointcount = this.SmoothPointCount;
            ConcurrentDictionary<double[], double> areadatas = new ConcurrentDictionary<double[], double>();//512份区间内的数量
            ConcurrentDictionary<int, double[]> areadictionaries = new ConcurrentDictionary<int, double[]>();//区间顺序
            for (int i = 0; i < count; i++)
            {
                var x1 = xAxis.GetAxisValueLocation(range * i + minaxis, xGetLocationParam);
                var x2 = xAxis.GetAxisValueLocation(range * (i + 1) + minaxis, xGetLocationParam);
                var key = new double[] { x1, x2 };
                areadatas[key] = 0;
                areadictionaries[i] = key;
            }
            var tempindexs = area.InsideIndexs;
            if (indexs != null) tempindexs = tempindexs.Intersect(indexs).ToArray();//取交集
            try
            {
                var result = Parallel.ForEach(tempindexs, new ParallelOptions() { MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i, loop) =>
                {
                    if (loop.IsStopped) return;
                    if (xSource == null || xSource.Count <= i) loop.Stop();
                    else
                    {
                        var tempv = xValueConverter == null ? Convert.ToDouble(xSource[i]) : xValueConverter(xSource[i]);
                        AddValue(areadatas, areadictionaries, tempv, max, min, xvaluetoaxisvalue, minaxis, range, count);
                    }
                });
                if (!result.IsCompleted) return null;
            }
            catch (OperationCanceledException) { return null; }
            var stream = new StreamGeometry() { FillRule = FillRule.EvenOdd };
            using (StreamGeometryContext sgc = stream.Open())
            {
                if (!FillStreamGeometryContext(null, sgc, areadatas, ygetlocation, yconvertparam, height, smooth, smoothpointcount)) return null;
                sgc.Close();
            }
            stream.Freeze();
            return stream;
        }
        #endregion
    }
}
